Skip to content

feat(scope_definition): read action list from service spec by default - #458

Merged
agustincelentano merged 4 commits into
mainfrom
feat/scope-definition-available-actions-from-spec
Aug 3, 2026
Merged

feat(scope_definition): read action list from service spec by default#458
agustincelentano merged 4 commits into
mainfrom
feat/scope-definition-available-actions-from-spec

Conversation

@sebastiancorrea81

@sebastiancorrea81 sebastiancorrea81 commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • action_spec_names's default becomes null; when unset, scope_definition now falls back to available_actions from the fetched service-spec.json.tpl (added in feat: add available_actions to service-spec templates scopes#208) instead of a hardcoded 15-item list tuned for k8s.
  • data.tf's two for_each expressions (data.http.action_templates, data.external.action_specs) now read local.static_action_specs instead of var.action_spec_names directly — required for the fallback to actually reach the resources that fetch/create action specs (an initial version of this change missed this and would have hard-failed tofu plan for any caller relying on the new default; caught in review and fixed).
  • Added a lifecycle.precondition on nullplatform_service_specification.from_template (main.tf): if action_spec_names is left unset AND the fetched spec doesn't have available_actions yet, tofu plan now fails loudly with a clear message instead of silently destroying every existing nullplatform_action_specification for that scope (an empty for_each is a destroy, not an error — caught in final review).
  • Every caller that already sets action_spec_names explicitly (scheduled_task, static-files, and the various account configs) is unaffected — the override still wins.

Merge/release ordering — please read before merging

repository_service_spec_branch defaults to main. As of this PR, nullplatform/scopes's main branch does not have available_actions yetnullplatform/scopes#208 targets beta, not main, and hasn't merged. The precondition above means this is no longer a silent-outage risk (plan will fail with a clear error instead), but to actually get the intended behavior:

  1. Merge and release feat: add available_actions to service-spec templates scopes#208 to beta, then let it flow to main.
  2. Confirm available_actions is present in main's spec templates (e.g. git show origin/main:k8s/specs/service-spec.json.tpl | grep available_actions).
  3. Only then merge/release this PR, or any caller not overriding action_spec_names will hit the new precondition error until step 1-2 land.

Test plan

  • tofu fmt -check / tofu validate pass
  • Local repro confirmed the for_each/null-default failure mode is fixed (without needing real credentials)
  • Local repro confirmed the new precondition fails plan in the dangerous case (var unset + spec lacks the field) and stays inert in both safe cases (var set; spec has the field)
  • Manual tofu plan against a live k8s scope (with scopes main already updated per the ordering note above) shows no changes (attach output before merge)

Depends on nullplatform/scopes#208 (adds available_actions to the service specs this module reads) reaching main, not just merging to beta.

sebastiancorrea81 and others added 2 commits July 31, 2026 17:28
…olve

Dropping the hardcoded default means a caller that relied on it, and whose spec
predates `available_actions`, now resolves to an empty list. That reaches the
action_specification for_each as zero instances and destroys every action
specification the scope has registered — with no error, since an empty list is
not one. Measured on a live implementation: 44 destroys across four scopes, in a
plan that reads as deliberate.

The precondition sits on the service specification rather than on the
action_specification resource because that resource has no instances when the
for_each is empty, so its own preconditions never run.

Callers passing action_spec_names explicitly are unaffected — verified against an
implementation where one scope keeps its list in locals and its spec has no
available_actions: the plan reports no changes.
@agustincelentano

Copy link
Copy Markdown
Collaborator

Agregué un commit a este branch (df701fbb) para cubrir el caso en que no se
resuelve ninguna acción. Contexto de por qué, con lo que medí probándolo contra una
implementación real:

Sin el default, un caller que dependía de él resuelve a [], y [] en el
for_each de nullplatform_action_specification no significa "dejá lo que había":
significa cero instancias. El plan propone destruir todas las action
specifications del scope, y no hay error porque una lista vacía no lo es.

Medido en implementation-aws antes de que los specs declararan sus acciones:

Plan: 0 to add, 0 to change, 44 to destroy

14  aws_lambda
15  containers
 8  scheduled_tasks
 7  static_files

Un plan que se lee como intencional y deja al scope sin crear/borrar scopes,
deployar, blue/green, rollback ni diagnósticos.

Dónde va el precondition. Lo puse en nullplatform_service_specification y no
en el action_specification, que sería el lugar intuitivo: con for_each vacío ese
recurso no tiene instancias, así que sus preconditions nunca se evalúan. El service
specification existe siempre.

Retrocompatibilidad. Verifiqué los dos caminos contra una implementación:

Caso Resultado
action_spec_names explícito, spec sin available_actions No changes
sin action_spec_names, spec sin available_actions corta en el plan con el mensaje

Así que quien pasa la lista no se ve afectado. Quien dependía del default recibe un
error que dice qué archivo tocar, en lugar de un apply destructivo.

Por otro lado, para que este PR se pueda mergear sin romper a los scopes que hoy
dependían del default, hicimos los specs que faltaban:

nullplatform/scopes ya las trae en feat/scope-definition-available-actions. Con
los cuatro alineados el plan da No changes, o sea que el refactor es transparente.

Sugerencia de orden: los dos PRs de specs primero (son inocuos mientras el módulo
ignore el campo), este último.

@agustincelentano

Copy link
Copy Markdown
Collaborator

Aporto el tofu plan contra una implementación real, que es el último item del test
plan. Corrido en implementation-aws (cuenta aws-services), con los cuatro scopes
apuntando a las ramas correspondientes:

scope repo / ref
containers scopes @ feat/scope-definition-available-actions
scheduled_tasks scopes @ feat/scope-definition-available-actions
aws_lambda scopes-lambda @ feat/declare-available-actions (nullplatform/scopes-lambda#35)
static_files scopes-static-files @ feat/declare-available-actions (nullplatform/scopes-static-files#21)

y action_spec_names eliminado del catálogo y del bloque del módulo:

$ tofu plan
No changes. Your infrastructure matches the configuration.

Cero creates, cero updates, cero destroys, para los cuatro scopes — 44 action
specifications en total. El refactor es transparente cuando los specs declaran sus
acciones.

Los dos casos de compatibilidad, probados por separado contra la misma
implementación:

Caso Resultado
action_spec_names explícito en el locals, spec sin available_actions No changes
sin action_spec_names, spec sin available_actions el precondition corta el plan

El segundo con este error, que es el comportamiento que buscábamos:

Error: Resource precondition failed
  │ local.static_action_specs is empty set of string

No actions resolved for scope "Static Scope".

Declare `available_actions` in static-files/specs/service-spec.json.tpl,
or pass `action_spec_names` explicitly.

Sobre el ordering: confirmo lo que dice la descripción — ni main ni beta de
scopes tienen available_actions hoy; solo el head de nullplatform/scopes#208. Y
lo mismo pasaba con scopes-lambda y scopes-static-files, que no tenían el campo
en ninguna versión: de ahí los dos PRs de arriba. Sin ellos, esos dos scopes caían al
precondition (antes de este último commit, a 21 destroys).

Dejo la rama de prueba en implementation-aws por si sirve para reproducirlo:
test/available-actions-from-spec.

@agustincelentano
agustincelentano merged commit 3695dbe into main Aug 3, 2026
46 checks passed
@agustincelentano
agustincelentano deleted the feat/scope-definition-available-actions-from-spec branch August 3, 2026 15:05
agustincelentano added a commit to nullplatform/scopes-lambda that referenced this pull request Aug 3, 2026
The list of actions a scope offers lived in the scope_definition module as a
hardcoded default, far from the scope that implements them. That distance is what
let them drift: the default listed "kill-instances" while the containers scope
ships "kill-instance", and the mismatched name resolved to a 404 whose body
reached jq as "404: Not Found" — a parse error that said nothing about a missing
file.

Declaring them here puts the list next to the action templates it names, so
adding or renaming an action is one change in one repository.

The fourteen match the action templates in specs/actions and the specifications
currently registered for this scope.

Requires the module to read the field: nullplatform/tofu-modules#458.
agustincelentano added a commit to nullplatform/scopes-static-files that referenced this pull request Aug 3, 2026
The list of actions a scope offers lived in the scope_definition module as a
hardcoded default, far from the scope that implements them. Declaring it here puts
the list next to the action templates it names.

The seven match the templates in specs/actions and the specifications currently
registered for this scope.

Requires the module to read the field: nullplatform/tofu-modules#458.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants